package legoserver;


import java.awt.*;
import java.awt.event.*;
import java.util.*;

/**
 * implementation of the classic A* search
 */
public class AStarClassic implements AStarPathFinder, Runnable {
  public final int NO_PATH = -1, NOT_FOUND = 0, FOUND = 1;
  int maxSteps = 1000;
  public Point[] solution = new Point[maxSteps];
  double minCost;
  Exception exception = null;
  protected Vector edge, done;
  protected AStarMap map;
  Thread loop;
  int factor = 30;
  private boolean busy;

  /**
   * calculates the weighted manhattan distance from a to b
   * @param a first point
   * @param b second point
   * @param low
   * @return manhattan distance
   */
  public double cbDist(Point a, Point b, double low) {
    return low * (Math.abs(a.x - b.x) + Math.abs(a.y - b.y) - 1);
  }

  /**
   * main part of the A* search, starts the loop thread
   * @param map table of grid cell with their attributes
   * @return an array of grid cells
   */
  public AStarGridCell[] findPath(AStarMap map) {
    busy = true;
    this.map = map;
    AStarGridCell.reset();
    edge = new Vector();
    done = new Vector();
    minCost = Double.MAX_VALUE;
    for (int i = 0; i < map.w; i++) {
      for (int j = 0; j < map.h; j++) {
        minCost = Math.min(map.gridCell[j][i].getCost(), minCost);
      }
    }
    // System.out.println("Cheepest Tile = " + minCost);
    if (AStarGridCell.getStartCell() == null) {
      System.out.println("no start point set!");
      busy = false;
      return null;
    }
    if (AStarGridCell.getFinishCell() == null) {
      System.out.println("no finish point set!");
      busy = false;
      return null;
    }
    System.out.println("calculating route");
    int startX = (int) map.getStartPosition().getX();
    int startY = (int) map.getStartPosition().getY();
    System.out.println("starting from X: " + startX + "  Y: " + startY);
    loop = new Thread(this);
    loop.start();
    busy = false;
    return null;
  }

  /**
   * overrides the run method of thread
   */
  public void run() {
    busy = true;
    edge.addElement(AStarGridCell.getStartCell());
    int pass = 0;
    boolean found = false;
    int state = NOT_FOUND;
    while (state == NOT_FOUND && pass < maxSteps) {
      pass++;
      state = step();
    }
    if (state == FOUND) { setPath(map); }
    else { System.out.println("no path found"); }
    busy = false;
  }
  
  public boolean isBusy() {
      return busy;
  }

  /**
   * calulates shortest distance by travelling stepwise the maze
   * @return int value with find status
   */
  public int step() {
    int tests = 0;
    boolean found = false;
    boolean growth = true;
    AStarGridCell finish = AStarGridCell.getFinishCell();
    Point end = finish.getPosition();
    Vector temp = (Vector) edge.clone();
    // find the most promising edge cell
    double min = Double.MAX_VALUE;
    double score;
    AStarGridCell best = (AStarGridCell) temp.elementAt(temp.size() - 1);
    AStarGridCell now;
    for (int i = 0; i < temp.size(); i++) {
      now = (AStarGridCell)temp.elementAt(i);
      if (!done.contains(now)) {
        score = now.getDistFromStart();
        score += cbDist(now.getPosition(), end, minCost);
        if (score < min) {
          min = score;
          best = now;
        }
      }
    }
    now = best;
    edge.removeElement(now);
    done.addElement(now);
    AStarGridCell next[] = map.getAdjacent(now);
    for (int i = 0; i < 4; i++) {
      if (next[i] != null) {
        if (next[i] == finish) { found = true; }
        if (!next[i].isTotalBlock()) {
          next[i].addToPathFromStart(now.getDistFromStart());
          tests++;
          if (!edge.contains(next[i]) && !done.contains(next[i])) {
            edge.addElement(next[i]);
            growth = true;
          }
        }
      }
      if (found) { return FOUND; }
    }
    //map.repaint();
    if (edge.size() == 0) { return NO_PATH; }
    // now process best
    return NOT_FOUND;
  }

  /**
   * enables the optimal route in the maze and starts the robot commander
   * @param map table of grid cell with their attributes
   */
  public void setPath(AStarMap map) {
    System.out.println("path found!");
    AStarGridCell.setShowPath(true);
    AStarGridCell next;
    AStarGridCell now = AStarGridCell.getFinishCell();
    AStarGridCell stop = AStarGridCell.getStartCell();
    int i = 0;
    // add last point to the solution array
    solution[i++] = now.getPosition();
    while (now != stop) {
      next = map.getLowestAdjacent(now);
      now = next;
      // add point to the solution array
      solution[i++] = now.getPosition();
      now.setPartOfPath(true);
      now.repaint();
    }
    System.out.println("done!");
    setCoordinates(getSolution());
  }


  public void setCoordinates(Vector solution){
    String valuesX = new String();
    String valuesY = new String();
    for (Enumeration el = solution.elements(); el.hasMoreElements(); ) {
      Point p = (Point) el.nextElement();
      valuesX = valuesX + (short) (factor * p.getX()) + ",";
      valuesY = valuesY + (short) (factor * p.getY()) + ",";
      // System.out.println("X: " + (short) p.getX() + "  Y: " + (short) p.getY());
    }
    // add values to the textfields and remove the last comma
    System.out.println(valuesX.substring(0, valuesX.length() - 1));
    System.out.println(valuesY.substring(0, valuesY.length() - 1));
  }
  
  /**
   * inverses the solution array and filters the edge points
   * @return vector with the coordinates of the edge points
   */
  public Vector getSolution() {
    boolean relative = true;
    int nowX = 0;
    int j = 0;
    int[] solChangeX = new int[maxSteps];
    Vector finalSol = new Vector();
    // get difference between x[i] and x[i-1]
    for (int i = 0; solution[i] != null; i++) {
      int nextX = (int) solution[i].getX();
      solChangeX[i] = nextX - nowX;
      nowX = nextX;
    }
    // fill only edges
    for (int i = 0; solution[i] != null; i++) {
      if (solChangeX[i] != solChangeX[i + 1]) {
        Point coord = getRelative(solution[i], map.getStartPosition());
        finalSol.insertElementAt(coord, 0);
      }
    }
    return finalSol;
    
    
  }

  /**
   * calculates the relative coordinates of a point (real - start)
   * @param coord absulute coordinate
   * @param start absulute start position
   * @return point with relative coordinate
   */
  public Point getRelative(Point coord, Point start) {
    int startX = (int) start.getX();
    int startY = (int) start.getY();
    int coordX = (int) coord.getX();
    int coordY = (int) coord.getY();
    Point newP = new Point(coordX - startX, startY - coordY);
    return newP;
  }
}